/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       combat_spell.cpp

	$Header: /game/combat_spell.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "combat_spell.h"

#include "adaptor_handler.h"
#include "artifact.h"
#include "artifact_prop_spell_charges.h"
#include "artifact_slot.h"
#include "artifact_type.h"
#include "battlefield.h"
#include "bound_handler.h"
#include "can_cast.h"
#include "combat_creature.h"
#include "combat_sounds.h"
#include "external_string.h"
#include "format_string.h"
#include "function_handler.h"
#include "handler.h"
#include "missile_type.h"
#include "music.h"
#include "pixel_24.h"
#include "replace_keywords.h"
#include "sound.h"
#include "spell_properties.h"


namespace
{
	t_external_string const k_combat_action_cast_spell( "combat_action.cast_spell" );
	t_external_string const k_combat_action_mirror_spell( "combat_action.mirror_spell" );
	t_external_string const k_combat_action_use_artifact( "combat_action.use_artifact" );
	t_external_string const k_combat_action_mirror_artifact( "combat_action.mirror_artifact" );

	class t_magic_leech_action : public t_handler_base
	{
	public:
		t_magic_leech_action( t_combat_creature_ptr caster, int power );

		virtual void operator()();

		void receive_power( t_combat_creature_ptr, t_map_point_2d, t_handler ref );
	protected:
		t_combat_creature_ptr m_source;
		t_combat_creature_ptr m_client;
		int					  m_power;
	};
};

// ------------------------------------------------------------------
// base class for combat spellcasting handlers
// ------------------------------------------------------------------
t_combat_spell::t_combat_spell( t_battlefield& battlefield, t_spell spell )
			  : m_battlefield( battlefield )
{
	m_is_artifact = false;
	m_artifact_slot = k_artifact_slot_none;
	m_backpack_slot = -1;
	m_spell = spell;
	m_cast_spell = spell;
	m_caster = battlefield.get_acting_creature();
	m_artifact_type = k_artifact_none;
}

void t_combat_spell::ai_cast_spell()
{
	m_battlefield.set_spell( this );
	begin_casting();
}

double t_combat_spell::ai_weight( t_combat_creature const& target ) const
{
	return 0.0;
}

// ------------------------------------------------------------------
// base class for combat spellcasting handlers
// ------------------------------------------------------------------
bool t_combat_spell::can_cast( t_screen_point const& point ) const
{
	return false;
}

void t_combat_spell::cast_and_mirror( t_combat_creature_ptr target, int power )
{
	cast_on( m_caster, target, power, false );
}

bool t_combat_spell::cast_on( t_combat_creature_ptr caster, 
		                      t_combat_creature_ptr target, 
							  int power, 
							  bool is_mirror )
{
	return false;
}

std::string t_combat_spell::get_action_text( bool is_mirror ) const
{
	if ( is_mirror )
	{
		if ( m_is_artifact )
		{
			return replace_keywords( k_combat_action_mirror_artifact, "%artifact", m_artifact_name );
		}
		else
		{
			return replace_keywords( k_combat_action_mirror_spell, "%spell", get_short_spell_name( m_cast_spell ) );
		}
	}
	else 
	{
		if ( m_is_artifact )
		{
			return replace_keywords( k_combat_action_use_artifact, "%artifact", m_artifact_name );
		}
		else
		{
			return replace_keywords( k_combat_action_cast_spell, "%spell", get_short_spell_name( m_cast_spell ) );
		}
	}
}


void t_combat_spell::left_button_down( t_screen_point const& screen_point )
{
}

void t_combat_spell::execute_mirror_spell( t_combat_creature_ptr, int power )
{
}

bool t_combat_spell::can_cast_on( t_combat_creature* creature, 
		                          std::string*       help_text ) const
{
	return ::can_cast( *m_caster, m_spell, *creature, help_text );
}

// ------------------------------------------------------------------
// base class for combat spellcasting handlers
// ------------------------------------------------------------------
t_mouse_window* t_combat_spell::mouse_move( t_screen_point const& point,
											std::string& help_text )
{
	return 0;
}

// ------------------------------------------------------------------
// base class for combat spellcasting handlers
// ------------------------------------------------------------------
bool t_combat_spell::left_click( t_screen_point const& point )
{
	return false;
}

static t_external_string const k_combat_action_spell_shackle_effect( "combat_action.spell_shackle_effect" );

// ------------------------------------------------------------------
// handle Spell Shackle
// ------------------------------------------------------------------
static void execute_spell_shackle( t_combat_creature_ptr caster, int damage )
{
	if (caster->get_number() == 0)
		return;

	t_battlefield& battlefield = caster->get_battlefield();
	t_combat_action_message message( *caster, k_combat_action_spell_shackle_effect );

	damage = caster->modify_spell_damage( caster, damage, k_spell_spell_shackle );
	battlefield.start_animation( *caster, k_spell_spell_shackle, message );
	caster->add_damage( damage, 0, false, false, message );
	caster->check_flinch( message );
	caster->apply_damage( battlefield, true );
}

// ------------------------------------------------------------------
// base class for combat spellcasting handlers
// ------------------------------------------------------------------
void t_combat_spell::pay_cost()
{
	t_battlefield& battlefield = m_battlefield;

	battlefield.get_data_cache().clear_paths();
	if (!m_is_artifact)
	{
		int cost = m_caster->get_spell_cost( m_spell );

		if (m_caster->is_active( k_spell_magic_leech ) && cost > 1)
			battlefield.add_action( new t_magic_leech_action( m_caster, cost / 2 ) );
		if (m_caster->is_active( k_spell_spell_shackle ))
		{
			t_handler handler;

			handler = add_argument( add_1st_argument( function_2_handler(
				                    execute_spell_shackle ), m_caster ), cost * k_spell_shackle_damage );
			battlefield.add_action( handler );
		}
		m_caster->change_spell_points( -cost );
		return;
	}

	if (m_artifact_slot == k_artifact_slot_none)
	{
		assert( m_backpack_slot >= 0);
		m_caster->remove_backpack( m_backpack_slot );
		return;
	}

	t_artifact const& artifact = m_caster->get_artifact( m_artifact_slot );

	t_artifact_effect_list const&	  effects = artifact.get_effects();
	t_artifact_effect*                effect;
	t_artifact_prop::t_spell_charges* charges;
	int								  effect_number;
	t_spell							  spell;

	for (effect_number = effects.size() - 1; effect_number >= 0; effect_number--)
	{
		effect = effects[effect_number];
		if (effect->get_effect() != k_artifact_effect_spell_charges)
			continue;
		charges = dynamic_cast<t_artifact_prop::t_spell_charges*>(effect);
		if (charges == 0)
			continue;

		spell = charges->get_spell();
		if (spell != m_spell)
			continue;

		charges->use_spell_points( get_spell_cost( m_spell ));
		return;
	}
	assert( false );
}

// ------------------------------------------------------------------
// base class for combat spellcasting handlers
// ------------------------------------------------------------------
void t_combat_spell::play_sound() const
{
	t_sound_ptr sound = get_combat_sound( m_spell );

	if (sound != 0)
		sound->play( get_sound_volume() );
}

// ------------------------------------------------------------------
// Set this spell to be artifact-based
// ------------------------------------------------------------------
void t_combat_spell::set_artifact( t_artifact_slot slot, int backpack_slot )
{
	m_is_artifact = true;
	m_artifact_slot = slot;
	m_backpack_slot = backpack_slot;

	// Record information about the artifact. This is necessary since 
	// pay_cost() will delete the artifact & we still need some data
	// about it later
	t_artifact const * artifact;
	if (m_artifact_slot != k_artifact_slot_none)
	{
		artifact = &m_caster->get_artifact( m_artifact_slot );
	}
	else
	{
		assert( m_backpack_slot >= 0);
		artifact = &m_caster->get_backpack_slot( m_backpack_slot );
	}

	m_artifact_name = artifact->get_name();
	m_artifact_type = artifact->get_icon();
}


static t_external_string const k_combat_action_mana_leech( "combat_action.mana_leech_spell" );

// ------------------------------------------------------------------
// handle a leech effect
// ------------------------------------------------------------------
t_magic_leech_action::t_magic_leech_action( t_combat_creature_ptr caster, int power )
{
	m_source = caster;
	m_client = caster->get_magic_leech_client();
	m_power  = power;
}

// ------------------------------------------------------------------
// handle a leech effect
// ------------------------------------------------------------------
void t_magic_leech_action::operator()()
{
	if (m_client->get_number() == 0)
		return;
	
	t_battlefield&    battlefield = m_client->get_battlefield();
	t_missile_handler handler;
	t_handler         ref = this;
	t_combat_action_message message( *m_client, k_combat_action_mana_leech );

	battlefield.start_animation( *m_source, k_spell_magic_leech, message );
	handler = add_3rd_argument( bound_handler( *this, 
		                        &t_magic_leech_action::receive_power ), ref );
	battlefield.launch_missile( k_missile_mana, *m_source, m_source->get_body_center(),
		                        m_client->get_body_center(), handler, message );
}

// ------------------------------------------------------------------
// handle a leech effect
// ------------------------------------------------------------------
void t_magic_leech_action::receive_power( t_combat_creature_ptr, t_map_point_2d, 
										  t_handler ref )
{
	t_battlefield&    battlefield = m_client->get_battlefield();

	m_client->change_spell_points( m_power );
	battlefield.add_text( *m_client, format_string( "+%i", m_power ),
		                  m_client->get_message_color(), k_message_icon_spell_points );
	battlefield.update_state();
}
